package com.zx.codegenerator;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.util.StringUtils;

import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.collect.Maps;
import com.zx.codegenerator.domain.MysqlColumnInfo;
import com.zx.codegenerator.domain.MysqlTableInfo;

import freemarker.template.Configuration;
import freemarker.template.Template;

/**
 * 修改自:http://git.oschina.net/wangao029/generate-jpa
 * @since 2017年4月23日
 */
public class Generate {

    private static final String MAVEN_JAVA_PATH = "src/main/java/";

    private static final String MAVEN_RESOURCES_PATH = "src/main/resources/";

    // 目标项目路径
    private static String targetProjectPath;

    private static Properties config;

    private static Configuration cfg;

    private DataSource ds = null;

    private NamedParameterJdbcTemplate jdbcTemplate = null;

    // db info enum
    enum DbType {
        MYSQL("com.mysql.jdbc.Driver", "SELECT * FROM information_schema.TABLES WHERE TABLE_SCHEMA='%s'", "SELECT * FROM Information_schema.COLUMNS WHERE TABLE_SCHEMA = '%s' and table_Name = '%s'"),

        POSTGRESQL("org.postgresql.Driver", "select tablename from pg_tables where schemaname='public'",
            "select column_name AS columnName,data_type AS dataType," + "(CASE WHEN is_nullable='YES' THEN true ELSE false END) AS isNullable,character_maximum_length AS charMaxLength,"
                                                                                                         + "(CASE WHEN position('nextval' in column_default)>0 THEN true ELSE false END) AS isPrimaryKey"
                                                                                                         + " from information_schema.columns" + " where table_schema = 'public' and table_name = '%s'"),

        SQLSERVER("net.sourceforge.jtds.jdbc.Driver",
            "SELECT Name as tablename FROM SysObjects Where XType='U' ORDER BY Name",
            "SELECT syscolumns.name as columnName," + "systypes.name as dataType," + "syscolumns.isnullable as isNullable," + "syscolumns.length AS charMaxLength,"
                                                                                      + "(CASE  WHEN (syscolumns.colid=sysindexkeys.keyno) THEN 1 ELSE 0 END) as isPrimaryKey "
                                                                                      + "FROM syscolumns, systypes ,sysindexkeys " + "WHERE syscolumns.xusertype = systypes.xusertype "
                                                                                      + "AND syscolumns.id = object_id('area') " + "AND syscolumns.id = sysindexkeys.id");

        private String jdbcDriver, allTableSQL, tableInfoSQL;

        DbType(String jdbcDriver, String allTableSQL, String tableInfoSQL) {
            this.jdbcDriver = jdbcDriver;
            this.allTableSQL = allTableSQL;
            this.tableInfoSQL = tableInfoSQL;
        }

        public String getJdbcDriver() {
            return jdbcDriver;
        }

        public String getAllTableSQL() {
            return allTableSQL;
        }

        public String getTableInfoSQL() {
            return tableInfoSQL;
        }
    }

    private static DbType dbType;

    private String dbName;

    /**
     * 初始化
     * @throws Exception
     */
    private void init()
        throws Exception {
        config = new Properties();
        config.load(Generate.class.getClassLoader().getResourceAsStream("codegenerator.properties"));
        // src/main/resources/
        String filePath = Generate.class.getClassLoader().getResource("template").getPath();
        System.out.println(filePath);
        config.setProperty("templatePath", filePath);

        // 初始化FreeMarker配置 创建一个Configuration实例,并设置FreeMarker的模版文件位置
        cfg = new Configuration();
        // cfg.setDirectoryForTemplateLoading(new File(config.getProperty("template.path")));
        URI uri = Generate.class.getClassLoader().getResource("template").toURI();
        cfg.setDirectoryForTemplateLoading(new File(uri));
        String uriRoot = uri.toString().replace("file:/", "");
        uriRoot = uriRoot.substring(0, uriRoot.indexOf("target")); // file:\
        // D:\ken\eclipseWorkspace\CodeGenerator\target\classes\template
        targetProjectPath = uriRoot;// config.get("target.project.path").toString();

        String jdbcDriver = config.getProperty("jdbc.driver");
        String jdbcUrl = config.getProperty("jdbc.url");
        // 创建数据库连接
        Class.forName(jdbcDriver);
        ds = new DriverManagerDataSource(jdbcUrl, config.getProperty("jdbc.username"), config.getProperty("jdbc.password"));
        jdbcTemplate = new NamedParameterJdbcTemplate(ds);

        if (DbType.POSTGRESQL.getJdbcDriver().equals(jdbcDriver)) {
            dbType = DbType.POSTGRESQL;
        } else if (DbType.MYSQL.getJdbcDriver().equals(jdbcDriver)) {
            dbType = DbType.MYSQL;
            // 获取mysql db
            Pattern p = Pattern.compile("/[a-z|_|1-9]+\\?");
            Matcher m = p.matcher(jdbcUrl);
            if (m.find()) {
                String target = m.group();
                dbName = m.group().substring(1, target.length() - 1);
            }
        } else if (DbType.SQLSERVER.getJdbcDriver().equals(jdbcDriver)) {
            dbType = DbType.SQLSERVER;
        }

    }

    /**
     * 断开db连接
     */
    private void destory() {
        try {
            if (ds != null) ds.getConnection().close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * 根据模板生成文件
     * @param root
     * @param projectPath
     * @param savePath
     * @param fileName
     * @param template
     */
    private void buildTemplate(Map root, String projectPath, String savePath, String fileName, Template template) {
        String realSavePath = GenerateUtils.appendBias(projectPath) + savePath;
        String realFileName = GenerateUtils.appendBias(realSavePath) + fileName;
        File newsDir = new File(realSavePath);
        if (!newsDir.exists()) {
            newsDir.mkdirs();
        }
        try {
            Writer out = new OutputStreamWriter(new FileOutputStream(realFileName), "UTF-8");
            template.process(root, out);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取表字段信息
     * @param table 表名
     * @return
     */
    private List<MysqlColumnInfo> getFieldInfo(String table) {
        String sql = String.format(dbType.getTableInfoSQL(), dbName, table);
        RowMapper<MysqlColumnInfo> mapper = new BeanPropertyRowMapper<MysqlColumnInfo>(MysqlColumnInfo.class);
        return jdbcTemplate.query(sql, new HashMap<String, Object>(), mapper);
    }

    /**
     * 获取所有表名
     * @return
     */
    private List<MysqlTableInfo> getAllTable() {
        String sql = String.format(dbType.getAllTableSQL(), dbName);
        RowMapper<MysqlTableInfo> mapper = new BeanPropertyRowMapper<MysqlTableInfo>(MysqlTableInfo.class);
        return jdbcTemplate.query(sql, new HashMap<String, Object>(), mapper);
        // return jdbcTemplate.queryForList(sql, new HashMap<String, Object>(), String.class);
    }
    // private List<String> getAllTable() {
    // String sql = String.format(dbType.getAllTableSQL(), dbName);
    // return jdbcTemplate.queryForList(sql, new HashMap<String, Object>(), String.class);
    // }

    /**
     * 生成基础model、dao、service
     * @param gc
     * @param ftlParams
     * @throws IOException
     */
    private static void generateBase(Generate gc, Map<String, Object> ftlParams)
        throws IOException {
        // 1 pagination
        String savePath = MAVEN_JAVA_PATH + GenerateUtils.appendBias(MoreObjects.firstNonNull(config.getProperty("ftl_package") + ".domain", "").replace(".", "/")) + "pagination";
        Template template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.Page.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "Page.java", template);

        savePath = MAVEN_JAVA_PATH + GenerateUtils.appendBias(MoreObjects.firstNonNull(config.getProperty("ftl_domain_package"), "").replace(".", "/")) + "pagination";
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.PageAttribute.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "PageAttribute.java", template);

        savePath = MAVEN_JAVA_PATH + GenerateUtils.appendBias(MoreObjects.firstNonNull(config.getProperty("ftl_domain_package"), "").replace(".", "/")) + "pagination";
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.PageList.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "PageList.java", template);

        savePath = MAVEN_JAVA_PATH + GenerateUtils.appendBias(MoreObjects.firstNonNull(config.getProperty("ftl_domain_package"), "").replace(".", "/")) + "pagination";
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.PaginationTemplate.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "PaginationTemplate.java", template);

        // 2 IMyBatisRepository&IPaginationDao
        savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_dao_package"), "").replace(".", "/");
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.IMyBatisRepository.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "IMyBatisRepository.java", template);

        savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_dao_package"), "").replace(".", "/");
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.IPaginationDao.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "IPaginationDao.java", template);

        // 3 my exception
        String fileName = config.get("ftl_exception_class") + ".java";
        savePath = MAVEN_JAVA_PATH + GenerateUtils.appendBias(MoreObjects.firstNonNull(config.getProperty("ftl_package"), "").replace(".", "/")) + "exception";
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.MyException.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);

        // 4 IBaseService&AbstractBaseService&IPaginationService
        savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_service_package"), "").replace(".", "/");
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.IBaseService.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "IBaseService.java", template);

        savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_service_package"), "").replace(".", "/");
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.AbstractBaseService.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "AbstractBaseService.java", template);
        // mybatis/mapper
        savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_service_package"), "").replace(".", "/");
        template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.IPaginationService.file"), ""));
        gc.buildTemplate(ftlParams, targetProjectPath, savePath, "IPaginationService.java", template);
    }

    /**
     * 去掉表的前缀.
     */
    private static String getSimpleTableNameFromTableName(String tableName) {
        try {
            tableName = tableName.toLowerCase();
            String tableSubFix[] = config.get("table.subfix").toString().split(",");
            if (tableSubFix != null && tableSubFix.length > 0) {
                for (String tmp : tableSubFix) {
                    if (tableName.startsWith(tmp.toLowerCase())) {
                        return tableName.substring(tmp.length());
                    }
                }
            }
        } catch (Exception e) {
            System.err.println("获取表名对应的domain名失败");
        }
        return tableName;
    }

    /**
     * 生成对应table的model，dao，service和mybatis配置文件
     * @param gc
     * @param ftlParams
     * @param isFirstGenerate
     * @throws IOException
     */
    private static void generateOther(Generate gc, Map<String, Object> ftlParams, Boolean isFirstGenerate)
        throws IOException {

        List<MysqlTableInfo> tables = gc.getAllTable();
        for (MysqlTableInfo v : tables) {
            if (v.getTable_name().contains(" ") || !v.getTable_name().equalsIgnoreCase("t_user_share_log")) {
                System.out.println("table " + v.getTable_name() + " contails blank char , skip");
                continue;
            }
            boolean hasDecimal = false;
            boolean hasDate = false;
            boolean hasId = false;
            List<MysqlColumnInfo> columnInfos = gc.getFieldInfo(v.getTable_name());
            String idType = "";
            String idColumn = "";
            String idFiled = "";
            for (MysqlColumnInfo tmp : columnInfos) {
                if (tmp.getType().equalsIgnoreCase("Date")) {
                    hasDate = true;
                }
                if (tmp.getType().equalsIgnoreCase("BigDecimal")) {
                    hasDecimal = true;
                }
                if (tmp.getColumn_key().equalsIgnoreCase("PRI")) {
                    hasId = true;
                    idType = tmp.getType();
                    idColumn = tmp.getColumnName();
                    idFiled = tmp.getField();
                }
            }
            String className = StringUtils.capitalize(GenerateUtils.convertCamel(getSimpleTableNameFromTableName(v.getTable_name())));
            ftlParams.put("tableName", v.getTable_name());
            ftlParams.put("tableInfo", v);
            ftlParams.put("className", className);
            ftlParams.put("classFields", columnInfos);
            ftlParams.put("hasDate", hasDate);
            ftlParams.put("hasDecimal", hasDecimal);
            ftlParams.put("hasId", hasId);
            ftlParams.put("idType", idType);
            ftlParams.put("idColumn", idColumn);
            ftlParams.put("idFiled", idFiled);
            ftlParams.put("hasAutoId", v.getAuto_increment() != null ? true : false);
            ftlParams.put("logicId", "f" + GenerateUtils.convertCamel(className) + "No");// 预定逻辑主键的命名方式

            // entity
            String fileName = className + ".java";
            String savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_domain_package"), "").replace(".", "/");
            Template template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.entity"), ""));
            gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);
            System.out.println("entity--->" + className);

            // Repository
            fileName = className + "Repository.java";
            savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_jpa_package"), "").replace(".", "/");
            template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.jpa"), ""));
            gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);
            System.out.println("Repository--->" + fileName);

            // service
            fileName = "I" + className + "Service.java";
            savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_service_package"), "").replace(".", "/");
            template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.service"), ""));
            gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);
            
            // service impl
            fileName = className + "Service.java";
            savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_service_package"), "").replace(".", "/") + "/impl";
            template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.service.impl"), ""));
            gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);

            // mybatis
            fileName = "I" + className + "Mapper.java";
            savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_dao_package"), "").replace(".", "/");
            template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.mybatis"), ""));
            gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);

            // mybatis xml 第一次会生成，以后不再生成，避免覆盖用户脚本
            if (true) {
                fileName = className + "Mapper.xml";
                template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.mybatis.mapper"), ""));
                gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);
            }
            
            // controller
            fileName = className + "Controller.java";
            savePath = MAVEN_JAVA_PATH + MoreObjects.firstNonNull(config.getProperty("ftl_controller_package"), "").replace(".", "/");
            template = cfg.getTemplate(MoreObjects.firstNonNull(config.getProperty("template.controller"), ""));
            gc.buildTemplate(ftlParams, targetProjectPath, savePath, fileName, template);
            // break;
        }
    }

    public static void main(String[] args)
        throws Exception {

        Generate gc = new Generate();
        gc.init();

        Map<String, Object> ftlParams = Maps.newHashMap();
        // 装填模板中的变量
        for (Object k : config.keySet()) {
            if (k.toString().startsWith("ftl")) {
                ftlParams.put(k.toString(), config.get(k).toString());
            }
        }

        ftlParams.put("ftl_now", new SimpleDateFormat("yyyy-MM-dd HH:mm").format(new Date()));
        ftlParams.put("ftl_dbType", dbType.toString());

        Object isFirstTmp = config.get("is.first.generate");

        // generateBase(gc, ftlParams);
        generateOther(gc, ftlParams, isFirstTmp == null || "true".equals(isFirstTmp.toString()));

        gc.destory();
        System.out.println("----------生成成功!");
    }

}
